﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PeoplePickerPlus.Entities;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint;
using System.Web;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Administration.Claims;

namespace PeoplePickerPlus.Helpers
{
    internal class EntityManager
    {
        public static int MaxEntityCount = 200;
        private const string USER_LIST_FIELDS = "<FieldRef Name='Name'/><FieldRef Name='Title'/><FieldRef Name='EMail'/><FieldRef Name='ContentType'/>";

        public static EntityGroup[] GetEntities(EntityRequest request)
        {
            SPPrincipalType principalType = GetPrincipalTypes(request.EntityTypes);

            EntityGroupList groups = new EntityGroupList();

            if (!LoadFromClaimsProviders(request, ref groups))
            {
                LoadFromPrincipals(request, principalType, SPPrincipalSource.All, ref groups);
            }

            if ((principalType & SPPrincipalType.SharePointGroup) == SPPrincipalType.SharePointGroup)
            {
                //load SP Groups
                LoadFromPrincipals(request, SPPrincipalType.SharePointGroup, SPPrincipalSource.UserInfoList, ref groups);
            }

            if ((principalType & SPPrincipalType.User) == SPPrincipalType.User)
            {
                //check email addresses
                GetEntitiesByEmailFromUserInfoList(request, ref groups);
            }

            if (!string.IsNullOrEmpty(request.CustomFilter))
            {
                return GetNameFilteredGroups(request.CustomFilter, groups);
            }
            //groups.Sort((EntityGroup e1, EntityGroup e2) => { return e2.Entities.Count.CompareTo(e1.Entities.Count); });
            
            return groups.ToArray();
        }

        private static EntityGroup[] GetNameFilteredGroups(string filter, EntityGroupList groups)
        {
            string[] filterParts = filter.Split('&');
            bool exclude = filterParts.Length > 1 && string.Equals(filterParts[1], "EXCLUDE", StringComparison.OrdinalIgnoreCase);
            FilterEntities(groups.Users.Entities, filterParts[0], exclude);
            FilterEntities(groups.Groups.Entities, filterParts[0], exclude);
            FilterEntities(groups.SecurityGroups.Entities, filterParts[0], exclude);
            FilterEntities(groups.Email.Entities, filterParts[0], exclude);

            return groups.ToArray();
        }

        private static void FilterEntities(List<EntityInfo> entities, string filter, bool exclude)
        {
            if (exclude)
            {
                entities.RemoveAll(item => item.Login.StartsWith(filter, StringComparison.OrdinalIgnoreCase));
            }
            else
            {
                entities.RemoveAll(item => !item.Login.StartsWith(filter, StringComparison.OrdinalIgnoreCase));
            }
        }

        private static void LoadFromPrincipals(EntityRequest request, SPPrincipalType principalType, SPPrincipalSource sources, ref EntityGroupList groups)
        {
            bool maxCountReached = false;

            IList<SPPrincipalInfo> results = SPUtility.SearchPrincipals(SPContext.Current.Web, request.Filter, principalType, sources, null, request.MaxCount, out maxCountReached);
            foreach (SPPrincipalInfo principal in results)
            {
                switch (principal.PrincipalType)
                {
                    case SPPrincipalType.User:
                        groups.Users.Entities.Add(new EntityInfo(principal));
                        break;

                    case SPPrincipalType.SharePointGroup:
                        groups.Groups.Entities.Add(new EntityInfo(principal));
                        break;

                    case SPPrincipalType.SecurityGroup:
                        groups.SecurityGroups.Entities.Add(new EntityInfo(principal));
                        break;
                }
            }
        }

        private static bool LoadFromClaimsProviders(EntityRequest request, ref EntityGroupList groups)
        {
            Uri context = GetContext(HttpContext.Current);

            if (!SPClaimProviderOperations.IsClaimsMode(context))
            {
                return false;
            }

            IList<PickerEntity> results = PopulateFromProviders(request.Filter, request.EntityTypes.Split(','), request.MaxCount);

            foreach (PickerEntity entity in results)
            {
                switch (entity.EntityType.ToLower())
                {
                    case "user":
                        groups.Users.Entities.Add(new EntityInfo(entity));
                        break;

                    case "sharepointgroup":
                        groups.Groups.Entities.Add(new EntityInfo(entity));
                        break;

                    case "secgroup":
                        groups.SecurityGroups.Entities.Add(new EntityInfo(entity));
                        break;
                }
            }

            return true;
        }

        private static IList<PickerEntity> PopulateFromProviders(string searchPattern, string[] entityTypes, int max)
        {
            List<PickerEntity> entities = new List<PickerEntity>();
            Uri context = GetContext(HttpContext.Current);

            if (SPClaimProviderOperations.IsClaimsMode(context))
            {
                SPProviderHierarchyTree[] array = SPClaimProviderOperations.Search(context, SPClaimProviderOperationOptions.AllZones, null, entityTypes, searchPattern, max);

                foreach (SPProviderHierarchyTree item in array)
                {
                    if (item.EntityData != null && item.EntityData.Count > 0)
                    {
                        foreach (PickerEntity entity in item.EntityData)
                        {
                            entities.Add(entity);
                        }
                    }
                }
            }

            return entities;
        }

        internal static Uri GetContext(HttpContext httpContext)
        {
            SPWeb contextWeb = SPControl.GetContextWeb(httpContext);
            return new Uri(contextWeb.Url);
        }

        private static SPPrincipalType GetPrincipalTypes(string entityTypes)
        {
            if (string.IsNullOrEmpty(entityTypes))
            {
                return SPPrincipalType.All;
            }

            SPPrincipalType types = SPPrincipalType.None;

            string[] typesList = entityTypes.Split(',');
            foreach (string type in typesList)
            {
                if (string.Equals(type, "User", StringComparison.OrdinalIgnoreCase))
                {
                    types ^= SPPrincipalType.User;
                    continue;
                }

                if (string.Equals(type, "SecGroup", StringComparison.OrdinalIgnoreCase))
                {
                    types ^= SPPrincipalType.SecurityGroup;
                    continue;
                }

                if (string.Equals(type, "SPGroup", StringComparison.OrdinalIgnoreCase))
                {
                    types ^= SPPrincipalType.SharePointGroup;
                    continue;
                }
            }

            return types;
        }

        public static void GetEntitiesByEmailFromUserInfoList(EntityRequest request, ref EntityGroupList groups)
        {
            SPQuery query = new SPQuery()
            {
                ViewFields = USER_LIST_FIELDS,
                IncludePermissions = false,
                IncludeAllUserPermissions = false,
                IncludeAttachmentUrls = false,
                IncludeAttachmentVersion = false,
                IncludeMandatoryColumns = false,
                RecurrenceOrderBy = false,
                ViewFieldsOnly = true,
                RowLimit = (uint)Math.Min(request.MaxCount, MaxEntityCount),
            };

            query.Query = string.Format("<Where><BeginsWith><FieldRef Name='EMail' /><Value Type='Text'>{0}</Value></BeginsWith></Where>",
                EncodeForCAML(request.Filter));

            SPListItemCollection results = SPContext.Current.Web.SiteUserInfoList.GetItems(query);

            groups.Email.Entities = GetEntitiesFromListItems(results);
        } 

        private static EntityInfo[] GetEntitiesFromPrincipals(IList<SPPrincipalInfo> principals)
        {
            List<EntityInfo> entities = new List<EntityInfo>();
            foreach (SPPrincipalInfo principal in principals)
            {
                entities.Add(new EntityInfo(principal));
            }

            return entities.ToArray();
        }

        private static List<EntityInfo> GetEntitiesFromListItems(SPListItemCollection items)
        {
            List<EntityInfo> entities = new List<EntityInfo>();
            foreach (SPListItem principal in items)
            {
                entities.Add(new EntityInfo(principal));
            }

            return entities;
        }

        public static EntityGroup[] GetMembers(string groupId)
        {
            bool reachedMax = false;

            SPPrincipalInfo[] members = SPUtility.GetPrincipalsInGroup(SPContext.Current.Web, groupId, MaxEntityCount, out reachedMax);

            EntityGroupList groups = new EntityGroupList();

            foreach (SPPrincipalInfo principal in members)
            {
                switch (principal.PrincipalType)
                {
                    case SPPrincipalType.User:
                        groups.Users.Entities.Add(new EntityInfo(principal));
                        break;

                    case SPPrincipalType.SecurityGroup:
                        //groups.SecurityGroups.Entities.Add(new EntityInfo(principal));
                        break;
                }
            }

            return groups.ToArray();
        }

        public static string EncodeForCAML(string s)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < s.Length; ++i)
            {
                char ch = s[i];
                if (char.IsLetterOrDigit(ch))
                    sb.Append(ch);
                else
                    sb.AppendFormat("&#{0};", Convert.ToInt32(ch));
            }
            return sb.ToString();
        }
    }
}
